package com.szeldon.declatest.reports;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Properties;
import java.util.logging.Logger;

import nu.xom.Attribute;
import nu.xom.Document;
import nu.xom.Element;

import com.szeldon.declatest.declaration.TestsSuiteDeclaration;
import com.szeldon.declatest.exec.TestSuiteExecHistory;
import com.szeldon.declatest.exec.events.ElseConditionEvaluationEndExecEvent;
import com.szeldon.declatest.exec.events.ElseConditionEvaluationStartExecEvent;
import com.szeldon.declatest.exec.events.IfConditionEvaluationEndExecEvent;
import com.szeldon.declatest.exec.events.IfConditionEvaluationStartExecEvent;
import com.szeldon.declatest.exec.events.ScenarioExecutionEndExecEvent;
import com.szeldon.declatest.exec.events.ScenarioExecutionStartExecEvent;
import com.szeldon.declatest.exec.events.TestSuiteExecEvent;
import com.szeldon.declatest.exec.events.dostep.DoScenarioStepEndExecEvent;
import com.szeldon.declatest.exec.events.dostep.DoScenarioStepStartExecEvent;
import com.szeldon.declatest.exec.events.dostep.jms.JmsMessageReceivedExecEvent;
import com.szeldon.declatest.exec.events.dostep.jms.JmsMessageSentExecEvent;
import com.szeldon.declatest.exec.result.EndScenarioExecutionResult;
import com.szeldon.declatest.exec.result.ErrorScenarioExecutionResult;
import com.szeldon.declatest.exec.result.NotComputedScenarioExecutionResult;
import com.szeldon.declatest.exec.result.ScenarioExecutionResult;
import com.szeldon.declatest.reports.exceptions.GeneratingReportException;
import com.szeldon.declatest.reports.plugins.IReportGeneratorPlugin;
import com.szeldon.utils.ValuesUtils;

/**
 * Generator of XML reports it has plugins inside that can use an XML created by this generator or may just use data
 * from objects.
 * 
 * @author szeldon
 * 
 */
public final class ReportGenerator {

    private static final String EXECUTION_RESULT_ATTRIBUTE_NAME_IN_XML = "executionResult";
    private static final String END_ATTRIBUTE_VALUE_IN_XML = "end";
    private static final String ERROR_ATTRIBUTE_VALUE_IN_XML = "error";
    private static final String NOT_COMPUTED_ATTRIBUTE_VALUE_IN_XML = "notComputed";

    private static Logger logger = Logger.getLogger(ReportGenerator.class.getName());

    private List<IReportGeneratorPlugin> generatorPlugins = new ArrayList<IReportGeneratorPlugin>();

    /**
     * 
     * @param generatorPlugin
     */
    public void addGeneratorPlugin(IReportGeneratorPlugin generatorPlugin) {
        generatorPlugins.add(generatorPlugin);
    }

    /**
     * 
     * @param suite
     * @param history
     * @param properties
     * @param xmlReportDirectory
     *            Path (absolute or not) into which XML report data will be put. MUST end with file separator.
     * @param reportPluginsDirectory
     *            Path (absolute or not) into which plugins will put their reports. MUST end with file separator.
     * @throws IOException
     *             Thrown when there was a problem while creating file for report.
     * @throws GeneratingReportException
     */
    public void generateReport(TestsSuiteDeclaration suite, TestSuiteExecHistory history, Properties properties,
            String xmlReportDirectory, String reportPluginsDirectory) throws IOException, GeneratingReportException {

        logger.info("Generating report for suite " + suite.getId() + " with XML data into directory "
                + xmlReportDirectory + " and plugins writing into " + reportPluginsDirectory + ".");

        String xmlReportFile = generateReportDataInXml(suite, history, properties, xmlReportDirectory);

        logger.fine("Passing execution to plugin reports. There are " + generatorPlugins.size() + " plugins.");
        // passes generating to plugins, so that they may create HTML and other report formats
        for (IReportGeneratorPlugin currentPlugin : generatorPlugins) {
            currentPlugin.generateReport(suite, history, properties,
                    reportPluginsDirectory + currentPlugin.getSubdirectoryName(), xmlReportFile);
        }

        logger.info("End of generating reports.");
    }

    /**
     * 
     * @param suite
     * @param history
     * @param properties
     * @param reportDirectory
     * @return Returns absolute path of XML file containing report data.
     * @throws IOException
     *             Thrown when there was a problem while creating file for report.
     */
    private String generateReportDataInXml(TestsSuiteDeclaration suite, TestSuiteExecHistory history,
            Properties properties, String reportDirectory) throws IOException {
        // suite
        Element rootElement = new Element("TestSuite");
        rootElement.addAttribute(new Attribute("id", history.getTestSuiteId()));
        rootElement.addAttribute(new Attribute("description", suite.getDescription()));

        // scenarios
        if (history.getScenariosIds().size() > 0) {
            Element allScenariosElement = new Element("Scenarios");
            for (String currentScenarioId : history.getScenariosIds()) {
                logger.fine("Generating report for scenario=" + currentScenarioId + ".");
                Element currentScenarioElement = generateElementForScenario(currentScenarioId, suite, history,
                        properties);
                allScenariosElement.appendChild(currentScenarioElement);
            }
            rootElement.appendChild(allScenariosElement);
        }

        // creates an XML with report data
        Document document = new Document(rootElement);
        Calendar calendar = GregorianCalendar.getInstance();
        String filename = reportDirectory + calendar.get(Calendar.YEAR)
                + ValuesUtils.convertToTwoDigitString(calendar.get(Calendar.MONTH))
                + ValuesUtils.convertToTwoDigitString(calendar.get(Calendar.DAY_OF_MONTH)) + "-"
                + ValuesUtils.convertToTwoDigitString(calendar.get(Calendar.HOUR_OF_DAY))
                + ValuesUtils.convertToTwoDigitString(calendar.get(Calendar.MINUTE))
                + ValuesUtils.convertToTwoDigitString(calendar.get(Calendar.SECOND)) + ".xml";
        logger.info("Rendering report data into " + filename + " file.");
        File file = new File(filename);
        if (!file.exists()) {
            File parent = file.getParentFile();
            logger.fine("Creating parent directories which are " + parent.getPath() + " if they don't exist (exist="
                    + parent.exists() + ").");
            if (!parent.exists()) {
                parent.mkdirs();
            }
            file.createNewFile();
        }

        FileWriter writer = new FileWriter(file);
        writer.write(document.toXML());
        writer.close();

        return file.getAbsolutePath();
    }

    /**
     * 
     * @param currentScenarioId
     * @param suite
     * @param history
     * @param properties
     * @return
     */
    private Element generateElementForScenario(String scenarioId, TestsSuiteDeclaration suite,
            TestSuiteExecHistory history, Properties properties) {
        //
        Element scenarioElement = new Element("Scenario");
        List<TestSuiteExecEvent> eventsForScenario = history.getEventsForScenario(scenarioId);
        scenarioElement.addAttribute(new Attribute("id", scenarioId));

        int currentEventIndex = 0;
        int eventsForScenarioSize = eventsForScenario.size();
        for (TestSuiteExecEvent currentEvent : eventsForScenario) {
            if (currentEventIndex != eventsForScenarioSize - 1) {
                Element currentElement = generateElementForEvent(scenarioId, suite, history, properties, currentEvent);
                scenarioElement.appendChild(currentElement);
            }
            currentEventIndex++;
        }

        Element scenarioEndElement = new Element("ScenarioExecutionEndExecEvent");
        handleScenarioEndEvent(scenarioId, suite, history, properties, scenarioEndElement, eventsForScenario);
        scenarioElement.appendChild(scenarioEndElement);

        return scenarioElement;
    }

    /**
     * 
     * @param scenarioId
     * @param suite
     * @param history
     * @param properties
     * @param scenarioEndElement
     * @param eventsForScenario
     */
    private void handleScenarioEndEvent(String scenarioId, TestsSuiteDeclaration suite, TestSuiteExecHistory history,
            Properties properties, Element scenarioEndElement, List<TestSuiteExecEvent> eventsForScenario) {
        TestSuiteExecEvent lastEvent = eventsForScenario.get(eventsForScenario.size() - 1);
        if (!(lastEvent instanceof ScenarioExecutionEndExecEvent)) {
            throw new IllegalStateException("Last event in history of execution has to be of "
                    + ScenarioExecutionEndExecEvent.class.getName() + " type. Currently it's of "
                    + lastEvent.getClass().getName() + " type.");
        }
        ScenarioExecutionEndExecEvent lastScenarioEvent = (ScenarioExecutionEndExecEvent) lastEvent;
        ScenarioExecutionResult result = lastScenarioEvent.getResult();
        SimpleDateFormat format = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss.SSS z");
        scenarioEndElement.addAttribute(new Attribute("eventDate", format.format(lastEvent.getEventDate())));
        scenarioEndElement.addAttribute(new Attribute("eventDescription", lastEvent.getEventDescription()));

        if (result instanceof EndScenarioExecutionResult) {
            scenarioEndElement.addAttribute(new Attribute(EXECUTION_RESULT_ATTRIBUTE_NAME_IN_XML,
                    END_ATTRIBUTE_VALUE_IN_XML));
        } else if (result instanceof ErrorScenarioExecutionResult) {
            scenarioEndElement.addAttribute(new Attribute(EXECUTION_RESULT_ATTRIBUTE_NAME_IN_XML,
                    ERROR_ATTRIBUTE_VALUE_IN_XML));
        } else if (result instanceof NotComputedScenarioExecutionResult) {
            scenarioEndElement.addAttribute(new Attribute(EXECUTION_RESULT_ATTRIBUTE_NAME_IN_XML,
                    NOT_COMPUTED_ATTRIBUTE_VALUE_IN_XML));
        } else {
            throw new IllegalStateException("Result of scenario's (scenarioId=" + scenarioId
                    + ") execution should be of " + EndScenarioExecutionResult.class.getName() + ", "
                    + ErrorScenarioExecutionResult.class.getName() + " or "
                    + NotComputedScenarioExecutionResult.class.getName() + " type. Currently it's of "
                    + result.getClass().getName() + " type.");
        }
    }

    /**
     * 
     * @param scenarioId
     * @param suite
     * @param history
     * @param properties
     * @param event
     * @return
     */
    private Element generateElementForEvent(String scenarioId, TestsSuiteDeclaration suite,
            TestSuiteExecHistory history, Properties properties, TestSuiteExecEvent event) {
        Element element = new Element(event.getClass().getSimpleName());

        if (event instanceof ScenarioExecutionStartExecEvent) {
            // currently nothing to do... used just not to get into else
        } else if (event instanceof IfConditionEvaluationStartExecEvent) {
            IfConditionEvaluationStartExecEvent ifEvent = (IfConditionEvaluationStartExecEvent) event;
            element.addAttribute(new Attribute("left", ifEvent.getLeft()));
            element.addAttribute(new Attribute("operator", ifEvent.getOperator()));
            element.addAttribute(new Attribute("right", ifEvent.getRight()));
            element.addAttribute(new Attribute("ifDescription", ifEvent.getIfDescription()));
        } else if (event instanceof ElseConditionEvaluationStartExecEvent) {
            ElseConditionEvaluationStartExecEvent elseEvent = (ElseConditionEvaluationStartExecEvent) event;
            element.addAttribute(new Attribute("left", elseEvent.getLeft()));
            element.addAttribute(new Attribute("operator", elseEvent.getOperator()));
            element.addAttribute(new Attribute("right", elseEvent.getRight()));
            element.addAttribute(new Attribute("ifDescription", elseEvent.getIfDescription()));
        } else if (event instanceof IfConditionEvaluationEndExecEvent) {
            IfConditionEvaluationEndExecEvent ifEvent = (IfConditionEvaluationEndExecEvent) event;
            element.addAttribute(new Attribute("evaluatedLeft", ifEvent.getEvaluatedLeft()));
            element.addAttribute(new Attribute("operator", ifEvent.getOperator()));
            element.addAttribute(new Attribute("evaluatedRight", ifEvent.getEvaluatedRight()));
            element.addAttribute(new Attribute("ifDescription", ifEvent.getIfDescription()));
            element.addAttribute(new Attribute("evaluationResult", "" + ifEvent.getEvaluationResult()));
        } else if (event instanceof ElseConditionEvaluationEndExecEvent) {
            ElseConditionEvaluationEndExecEvent elseEvent = (ElseConditionEvaluationEndExecEvent) event;
            element.addAttribute(new Attribute("evaluatedLeft", elseEvent.getEvaluatedLeft()));
            element.addAttribute(new Attribute("operator", elseEvent.getOperator()));
            element.addAttribute(new Attribute("evaluatedRight", elseEvent.getEvaluatedRight()));
            element.addAttribute(new Attribute("ifDescription", elseEvent.getIfDescription()));
            element.addAttribute(new Attribute("evaluationResult", "" + elseEvent.getEvaluationResult()));
        } else if (event instanceof DoScenarioStepStartExecEvent) {
            DoScenarioStepStartExecEvent doEvent = (DoScenarioStepStartExecEvent) event;
            element.addAttribute(new Attribute("endpointUrl", doEvent.getEndpointUrl()));
            element.addAttribute(new Attribute("stepDescription", doEvent.getStepDescription()));
            element.addAttribute(new Attribute("requestPath", doEvent.getStepRequestPath()));
        } else if (event instanceof DoScenarioStepEndExecEvent) {
            DoScenarioStepEndExecEvent doEvent = (DoScenarioStepEndExecEvent) event;
            element.addAttribute(new Attribute("endpointUrl", doEvent.getEndpointUrl()));
            element.addAttribute(new Attribute("stepDescription", doEvent.getStepDescription()));
            element.addAttribute(new Attribute("requestPath", doEvent.getStepRequestPath()));
        } else if (event instanceof JmsMessageSentExecEvent) {
            JmsMessageSentExecEvent reqEvent = (JmsMessageSentExecEvent) event;
            element.addAttribute(new Attribute("messageContent", reqEvent.getMessageContent()));
        } else if (event instanceof JmsMessageReceivedExecEvent) {
            JmsMessageReceivedExecEvent respEvent = (JmsMessageReceivedExecEvent) event;
            element.addAttribute(new Attribute("messageContent", respEvent.getMessageContent()));
        } else {
            throw new IllegalStateException("Event type (" + event.getClass().getName()
                    + ") is currently unsupported by report generator during generating report for scenarioId="
                    + scenarioId + ".");
        }

        SimpleDateFormat format = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss.SSS z");
        element.addAttribute(new Attribute("eventDate", format.format(event.getEventDate())));
        element.addAttribute(new Attribute("eventDescription", event.getEventDescription()));
        return element;
    }

}
